package com.toobigtofail.drum
{
	public class Measure
	{		
		private var _notes:Array = new Array;
		private var _nextNote:uint = 0;
		
		public function Measure(notesStr:String) {
			// Take in a string representing a measure, as in "ABA" and translate
			// it into an array of notes as defined by the enumerated types in Notes
			var currNoteSet:Array = new Array;
			var c:String;
			var mn:MeasureNote;
			for (var i:int = 0; i < notesStr.length; i++) {
				c = notesStr.charAt(i);
				if (c == ".") {
					addAllToNotes(currNoteSet);
					currNoteSet = new Array;
				}
				else {
					mn = new MeasureNote;
					mn.note = Notes.fromString(c);
					currNoteSet.push(mn);
				}
			}
			
			addAllToNotes(currNoteSet);
		}
		
		private function addAllToNotes(notes:Array):void {
			for each (var mn:MeasureNote in notes) {
				mn.time = 1/Number(notes.length);
				_notes.push(mn);
			}
		}
		
		public function checkNextNote(key:int, bpm:Number, lastHitTime:Number):HitResult {
			var result:HitResult = new HitResult(HitResult.FAILURE, 0);
			
			// set the offset between the expected time of drum hit and actual hit time in milliseconds
			result.timingOffset = (new Date).getTime() - nextDrumTime(bpm, lastHitTime);
			
			if ((_notes[_nextNote++] as MeasureNote).note == Notes.getNote(key)) {
				// the note was successful
				if (_nextNote == _notes.length) {
					// the measure was successful
					result.result = HitResult.MEASURE_SUCCESS;
				}
				else {
					result.result = HitResult.NOTE_SUCCESS;
				}
			}

			// reset if we finished the measure or failed to hit the right key
			switch (result.result) {
				case HitResult.FAILURE: reset(); break;
				case HitResult.MEASURE_SUCCESS: reset(); break;
			}
			
			return result;
		}
		
		public function reset():void {
			_nextNote = 0;
		}
		
		public function getNote(idx:int):int {
			if (_nextNote + idx >= _notes.length) {
				return Notes.INVALID;
			}
			return (_notes[_nextNote + idx] as MeasureNote).note;
		}
		
		public function get notesLeft():uint {
			return _notes.length - _nextNote;
		}
		
		public function get nextNote():int {
			return getNote(0);
		}
		
		/**
		 * returns the next time a drum hit is expected in milliseconds since the epoch
		 */ 
		public function nextDrumTime(bpm:Number, lastDrumTime:Number):Number {
			return lastDrumTime + millisBetweenBeats(bpm);
		}
		
		public function millisBetweenBeats(bpm:Number):Number {
			return (60000/bpm)*(_notes[_nextNote] as MeasureNote).time;
		}
	}
}